/** Vue EventBus 事件总线 */

function eventsMixin(Vue){
	Vue.prototype.$on = function(event, fn){}
	Vue.prototype.$once = function(event,fn){}
	Vue.prototype.$off = function(event,fn){}
	Vue.prototype.$emit = function(event,fn){}
}

Vue.prototype.$on = function (event, fn) {
  const vm = this
  // 我们传入的要监听的事件可能为数组，这时候对数组里的每个事件再递归调用$on方法
  if (Array.isArray(event)) {
    for (let i = 0, l = event.length; i < l; i++) {
      vm.$on(event[i], fn);
    }
  } else {
    // 之前已经有监听event事件，则将此次监听的回调函数添加到其数组中，否则创建一个新数组并添加fn
    (vm._events[event] || (vm._events[event] = [])).push(fn);
  }
  return vm
}


Vue.prototype.$emit = function(event){
	const vm = this;
	const cbs = vm._events[event];
	if(cbs){
    cbs = cbs.length > 1 ? toArray(cbs) : cbs;
    // $emit方法可以传参，这些参数会在调用回调函数的时候传进去
		const args = toArray(arguments,1);
		for(let i = 0,l=cbs.length; i<l;i++){
			try{
				cbs[i].apply(vm,args);
			}catch(e){
				handleError(e,vm,("event handler for \"" + event + "\""));
			}
		}
	}
	return vm;
}

Vue.prototype.$once = function (event, fn) {
  const vm = this
  // 封装一个高阶函数on，在on里面调用fn
  function on () {
    // 每当执行了一次on，移除event里的on事件，后面再触发event事件就不会再执行on事件了，也就不会执行on里面的fn事件
    vm.$off(event, on);
    // 执行on的时候，执行fn函数
    fn.apply(vm, arguments);
  }
  // 这个赋值是在$off方法里会用到的
  // 比如我们调用了vm.$off(fn)来移除fn回调函数，然而我们在调用$once的时候，实际执行的是vm.$on(event, on)
  // 所以在event的回调函数数组里添加的是on函数，这个时候要移除fn，我们无法在回调函数数组里面找到fn函数移除，只能找到on函数
  // 我们可以通过on.fn === fn来判断这种情况，并在回调函数数组里移除on函数
  on.fn = fn;
  // $once最终调用的是$on，并且回调函数是on
  vm.$on(event, on);
  return vm;
}


Vue.prototype.$off = function (event, fn) {
  const vm = this;
  // all
  if (!arguments.length) {
  // 如果没有传参数，则清空所有事件的监听函数
    vm._events = Object.create(null);
    return vm;
  }
  // 如果传的event是数组，则对该数组里的每个事件再递归调用$off方法
  if (Array.isArray(event)) {
    for (let i = 0, l = event.length; i < l; i++) {
      vm.$off(event[i], fn);
    }
    return vm;
  }
  // 获取当前event里所有的回调函数
  const cbs = vm._events[event]
  // 如果不存在回调函数，则直接返回，因为没有可以移除监听的内容
  if (!cbs) {
    return vm;
  }
  // 如果没有指定要移除的回调函数，则移除该事件下所有的回调函数
  if (!fn) {
    vm._events[event] = null
    return vm;
  }
  // 指定了要移除的回调函数
  let cb;
  let i = cbs.length;
  while (i--) {
    cb = cbs[i];
    // 在事件对应的回调函数数组里面找出要移除的回调函数，并从数组里移除
    if (cb === fn || cb.fn === fn) {
      cbs.splice(i, 1);
      break;
    }
  }
  return vm;
}
